#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "liso_engine.h"
#include "liso_logging.h"

/* global variables */
pool_t pool;
int pipe2sock[FD_SETSIZE] = {-1};

/* select engine -- helper routines */
int open_listenfd(int port);
void init_pool(pool_t *p);
int add_client(int httpfd);
int add_ssl_client(int sslfd);
void liso_engine_client_handler(int fd, select_engine_t *engine);
int liso_engine_recv(int fd);
int liso_engine_send(int fd);
int liso_engine_ssl_recv(int fd);
int liso_engine_ssl_send(int fd);
int liso_engine_pipe_recv(int fd);

void liso_engine_create(select_engine_t *engine, 
						int port, 
						int sslport, 
						char *log, 
						char *lock,
					    char *www,	
						char *cgi,
						char *pkey, 
						char *cert)
{
	engine->httpport = port;
	engine->sslport = sslport;
	engine->logfile = log;
	engine->lockfile = lock;
	engine->www = www;
	engine->cgi = cgi;
	engine->privatekey = pkey;
	engine->cert = cert;

	/* initialize global variables */
	init_pool(&pool);

	/************ SSL INIT ************/
	SSL_load_error_strings();
	SSL_library_init();

	if ((pool.ssl_context = SSL_CTX_new(TLSv1_server_method())) == NULL) {
		liso_logging_log("liso_engine", "liso_engine_create", 
							"Error creating SSL context.\n");
		liso_engine_shut_down(EXIT_FAILURE);	
	}

	if (SSL_CTX_use_PrivateKey_file(pool.ssl_context, engine->privatekey, 
										SSL_FILETYPE_PEM) == 0) {
		SSL_CTX_free(pool.ssl_context);
		liso_logging_log("liso_engine", "liso_engine_create", 
							"Error associating private key.\n");
		liso_engine_shut_down(EXIT_FAILURE);	
	}

	 if (SSL_CTX_use_certificate_file(pool.ssl_context, engine->cert,
                                     SSL_FILETYPE_PEM) == 0) {
		SSL_CTX_free(pool.ssl_context);
		liso_logging_log("liso_engine", "liso_engine_create", 
							"Error associating certificate.\n");
		liso_engine_shut_down(EXIT_FAILURE);	
	}
	/************ END SSL INIT ************/
}

void liso_engine_register_http_handler(select_engine_t* engine, http_handler hdl)
{
	engine->hdl = hdl;
}

void liso_engine_set_pipe2sock(int pipefd, int sockfd)
{
	int flag;
	/* set sockfd to pipe2sock array */
	pipe2sock[pipefd] = sockfd;
	/* set pipefd to proper fd set */
	FD_SET(pipefd, &pool.pipe_set);
	FD_SET(pipefd, &pool.read_set);
	/* set pipefd to nonblocking mode */
	flag = fcntl(pipefd, F_GETFL, 0);
	fcntl(pipefd, F_SETFL, flag | O_NONBLOCK);
	/* modify pool.maxfd if necessary */
	if (pool.maxfd < pipefd)
		pool.maxfd = pipefd;
}

int liso_engine_event_loop(select_engine_t *engine)
{
	int httpfd, sslfd, clientfd;
	int i, flag;

	if ((httpfd = open_listenfd(engine->httpport)) < 0) {
		SSL_CTX_free(pool.ssl_context);
		liso_logging_log("liso_engine", "liso_engine_event_loop", 
							"Error opening http listening socket.\n");
		liso_engine_shut_down(EXIT_FAILURE);
	}

	if ((sslfd = open_listenfd(engine->sslport)) < 0) {
		SSL_CTX_free(pool.ssl_context);
		liso_logging_log("liso_engine", "liso_engine_event_loop", 
							"Error opening ssl listening socket.\n");
		liso_engine_shut_down(EXIT_FAILURE);
	}
	

	/* add httpfd and sslfd to pool */
	pool.maxfd = httpfd > sslfd ? httpfd : sslfd;
	FD_SET(httpfd, &pool.read_set);
	FD_SET(sslfd, &pool.read_set);
	
	liso_logging_log("liso_engine", "liso_engine_event_loop",
						"Liso Server Listening...\n");

	/* select engine -- main loop */
	while (1) {
		pool.ready_set = pool.read_set;
		pool.nready = select(pool.maxfd+1, &pool.ready_set, &pool.write_set, NULL, NULL);

		if (pool.nready == -1) {
			if (errno == EINTR) 
				continue;
			else {
				close(httpfd);
				close(sslfd);
				liso_logging_log("liso_engine", "liso_engine_event_loop",
									"Error selecting fds.\n");
				liso_engine_shut_down(EXIT_FAILURE);
			}
		}

		/* if the http listening descriptor is ready, add new client to pool */
		if (FD_ISSET(httpfd, &pool.ready_set)) {
			if ((clientfd = add_client(httpfd)) == -1) 
				liso_engine_shut_down(EXIT_FAILURE);
			pool.cliconn[clientfd].port = engine->httpport;
		}		
		
		if (FD_ISSET(sslfd, &pool.ready_set)) {
			if ((clientfd = add_ssl_client(sslfd)) == -1) 
				liso_engine_shut_down(EXIT_FAILURE);
			else if (clientfd == -2)
				continue;
			else {
				/* set ssl client socket to nonblocking mode */
				flag = fcntl(clientfd, F_GETFL, 0);
				fcntl(clientfd, F_SETFL, flag | O_NONBLOCK);
				pool.cliconn[clientfd].port = engine->sslport;
			}	
		}
		
		for (i = 0; (i <= pool.maxfd) && (pool.nready > 0); i++) {
			if (i == httpfd || i == sslfd)
				continue;
			else if (FD_ISSET(i, &pool.ready_set)) {
				liso_engine_client_handler(i, engine);
			}
			else if (FD_ISSET(i, &pool.write_set)) {
				if (FD_ISSET(i, &pool.ssl_set))
					liso_engine_ssl_send(i);
				else				
					liso_engine_send(i);
				FD_CLR(i, &pool.write_set);
				reset_cliconn(&pool.cliconn[i]);
			}
		}
	}

	/* program control flow should never reach here */
	return EXIT_SUCCESS;
}


void init_pool(pool_t *p)
{
	p->maxfd = -1;
	FD_ZERO(&p->read_set);
	FD_ZERO(&p->ssl_set);
	FD_ZERO(&p->pipe_set);
	FD_ZERO(&p->write_set);
	FD_ZERO(&p->ready_set);
	p->nready = 0;
}


int open_listenfd(int port)
{
	int listenfd, optval = 1;
	struct sockaddr_in serveraddr;

	/* create a socket descriptor */
	if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		return -1;

	/* eliminates "Address already in use" error from bind */
	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
				(const void *) &optval, sizeof(int)) < 0)
		return -1;

	/* bind IP address and port to listenfd */
	bzero((char *) &serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons((unsigned short) port);
	if (bind(listenfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0)
		return -1;

	if (listen(listenfd, LISTENQ) < 0)
		return -1;

	return listenfd;
}


int add_client(int httpfd)
{
	int clientfd;
	socklen_t clientlen = sizeof(struct sockaddr_in);
	struct sockaddr_in clientaddr;

	/* accept client's connection */
	if ((clientfd = accept(httpfd, (struct sockaddr *) &clientaddr,
					&clientlen)) < 0) {
		close(httpfd);
		liso_logging_log("liso_engine", "add_client",
							"Error accepting new connection: %s\n", strerror(errno));
		return -1;
	}

	/* initialize client connection structure */
	if (init_cliconn(&pool.cliconn[clientfd])) {	/* init failed */
		close(clientfd);
	}
	else {	/* init success */
		memcpy(&pool.cliconn[clientfd].clientaddr, &clientaddr, sizeof(clientaddr));
		FD_SET(clientfd, &pool.read_set);
		if (pool.maxfd < clientfd)
			pool.maxfd = clientfd;
	}

	liso_logging_log("liso_engine", "add_client",
						"Liso server accepted new connection from %s:%d with fd %d.\n",
						inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port, clientfd);
	return clientfd;
}

int add_ssl_client(int sslfd)
{
	int clientfd;

	if ((clientfd = add_client(sslfd)) == -1)
		return -1;
	/************ WRAP SOCKET WITH SSL ************/
	if ((pool.cliconn[clientfd].client_context = SSL_new(pool.ssl_context)) == NULL) {
		liso_logging_log("liso_engine", "add_ssl_client",
							"Error creating client SSL context.\n");
		close(sslfd);
        SSL_CTX_free(pool.ssl_context);
		return -1;
	}
        	
	if (SSL_set_fd(pool.cliconn[clientfd].client_context, clientfd) == 0) {
		liso_logging_log("liso_engine", "add_ssl_client",
							"Error creating client SSL context.\n");
		close(sslfd);
		SSL_free(pool.cliconn[clientfd].client_context);
        SSL_CTX_free(pool.ssl_context);
		return -1;
	}

    if (SSL_accept(pool.cliconn[clientfd].client_context) <= 0) {
		liso_logging_log("liso_engine", "add_ssl_client",
							"Error accepting (handshake) client SSL context.\n");
		FD_CLR(clientfd, &pool.read_set);
		close(clientfd);
		SSL_shutdown(pool.cliconn[clientfd].client_context);
		SSL_free(pool.cliconn[clientfd].client_context);
		return -2;
	}
    /************ END WRAP SOCKET WITH SSL ************/
	pool.cliconn[clientfd].is_ssl = 1;	
	FD_SET(clientfd, &pool.ssl_set);	
	return clientfd;
}

void liso_engine_client_handler(int fd, select_engine_t *engine)
{
	int ret;
	if (FD_ISSET(fd, &pool.pipe_set)) 
		ret = liso_engine_pipe_recv(fd);	
	else if (FD_ISSET(fd, &pool.ssl_set))
		ret = liso_engine_ssl_recv(fd);
	else
		ret = liso_engine_recv(fd);
	
	switch (ret) {
		case LE_RECV_SUCCESS:
			ret = engine->hdl(fd, &pool.cliconn[fd], engine);
			if (ret < 0) {
				if (FD_ISSET(fd, &pool.ssl_set)) 
					FD_CLR(fd, &pool.ssl_set);
				FD_CLR(fd, &pool.read_set);
				clear_cliconn(&pool.cliconn[fd]);
				close(fd);
			}
			else if (ret == 0) {
				FD_SET(fd, &pool.write_set);
			}
			else if (ret == 1) { 
				//reset_cliconn(&pool.cliconn[fd]);
			}
			break;
		case LE_RECV_EOF:
			if (FD_ISSET(fd, &pool.pipe_set)) {
				/* add sockfd to write set */
				FD_SET(pipe2sock[fd], &pool.write_set);
				/* clear state */
				FD_CLR(fd, &pool.pipe_set);
				FD_CLR(fd, &pool.read_set);
				close(fd);
				break;
			}
		case LE_RECV_FAIL:
			if (FD_ISSET(fd, &pool.pipe_set)) {
				FD_CLR(fd, &pool.pipe_set);
				FD_CLR(fd, &pool.read_set);
				close(fd);
				fd = pipe2sock[fd];
			}

			if (FD_ISSET(fd, &pool.ssl_set)) 
				FD_CLR(fd, &pool.ssl_set);
			FD_CLR(fd, &pool.read_set);
			clear_cliconn(&pool.cliconn[fd]);
			close(fd);
			break;
	}
}

int liso_engine_recv(int fd)
{
	int ret;
	char buf[BUF_SIZE];
	liso_buf_t *lisobuf; 

	lisobuf = pool.cliconn[fd].recvbuf;
	
	while ((ret = recv(fd, buf, BUF_SIZE, MSG_DONTWAIT)) != 0) {
		if (ret < 0) {
			if (errno == EINTR)
				continue;
			else if (errno == EAGAIN)
				return LE_RECV_SUCCESS;
			else {
				liso_logging_log("liso_engine", "liso_engine_recv",
							"Error receiving data from client %d: %s\n", fd, strerror(errno));
				return LE_RECV_FAIL;
			}
		}
		else {
			if (lisobuf->buflen + ret > MAX_REQ_LEN) {
				liso_logging_log("liso_engine", "liso_engine_recv",
									"Client's request is longer than MAX_REQ_LEN.\n");
				return LE_RECV_FAIL;
			}
			else {
				if ((lisobuf->buflen + ret) > lisobuf->bufsize) {
					if (extend_liso_buf(lisobuf)) 
						return LE_RECV_FAIL;
				}
				memcpy(lisobuf->liso_buf + lisobuf->buflen, buf, ret);
				lisobuf->buflen += ret;
			}
		}
	}

	return LE_RECV_EOF;
}

int liso_engine_send(int fd)
{
	int sendlen;
	liso_buf_t *lisobuf;
	lisobuf = pool.cliconn[fd].sendbuf;
	
	if ((sendlen = send(fd, lisobuf->liso_buf, lisobuf->buflen, MSG_DONTWAIT)) != lisobuf->buflen) {
		liso_logging_log("liso_engine", "liso_engine_send",
							"Error sending data to client %d.\n", fd);			
		return LE_SEND_FAIL;
	}

	return LE_SEND_SUCCESS;
}

int liso_engine_ssl_recv(int fd)
{
	int ret;
	char buf[BUF_SIZE];
	liso_buf_t *lisobuf; 

	lisobuf = pool.cliconn[fd].recvbuf;
	
	while ((ret = SSL_read(pool.cliconn[fd].client_context, buf, BUF_SIZE)) != 0) {
		if (ret < 0) {
			if (errno == EINTR)
				continue;
			else if (errno == EAGAIN)
				return LE_RECV_SUCCESS;
			else {
				liso_logging_log("liso_engine", "liso_engine_ssl_recv",
							"Error receiving data from client %d: %s\n", fd, strerror(errno));
				return LE_RECV_FAIL;
			}
		}
		else {
			if (lisobuf->buflen + ret > MAX_REQ_LEN) {
				liso_logging_log("liso_engine", "liso_engine_ssl_recv",
									"Client's request is longer than MAX_REQ_LEN.\n");
				return LE_RECV_FAIL;
			}
			else {
				if ((lisobuf->buflen + ret) > lisobuf->bufsize) {
					if (extend_liso_buf(lisobuf)) 
						return LE_RECV_FAIL;
				}
				memcpy(lisobuf->liso_buf + lisobuf->buflen, buf, ret);
				lisobuf->buflen += ret;
			}
		}
	}
	
	return LE_RECV_EOF;
}

int liso_engine_ssl_send(int fd)
{
	int sendlen;
	liso_buf_t *lisobuf;
	lisobuf = pool.cliconn[fd].sendbuf;
	
	if ((sendlen = SSL_write(pool.cliconn[fd].client_context, lisobuf->liso_buf, 
							lisobuf->buflen)) != lisobuf->buflen) {
		liso_logging_log("liso_engine", "liso_engine_ssl_send",
							"Error sending data to client %d.\n", fd);			
		return LE_SEND_FAIL;
	}

	return LE_SEND_SUCCESS;
}

int liso_engine_pipe_recv(int fd)
{
	int ret;
	int sockfd = pipe2sock[fd];
	char buf[BUF_SIZE];
	liso_buf_t *lisobuf; 

	if (pool.cliconn[sockfd].sendbuf == NULL) {
		if ((pool.cliconn[sockfd].sendbuf = make_new_buf()) == NULL)
			return LE_RECV_FAIL;
	}

	lisobuf = pool.cliconn[sockfd].sendbuf;

/*	while((ret = read(fd, buf, BUF_SIZE)) > 0) {
		if ((lisobuf->buflen + ret) > lisobuf->bufsize) {
			if (extend_liso_buf(lisobuf)) 
				return LE_RECV_FAIL;
		}
		memcpy(lisobuf->liso_buf + lisobuf->buflen, buf, ret);
		lisobuf->buflen += ret;
	}

	if (ret == 0) {
		return LE_RECV_EOF;
	}
	else
		return LE_RECV_FAIL;	*/
	while ((ret = read(fd, buf, BUF_SIZE)) != 0) {
		if (ret < 0) {
			if (errno == EINTR)
				continue;
			else if (errno == EAGAIN)
				return LE_RECV_EOF;
			else {
				liso_logging_log("liso_engine", "liso_engine_pipe_recv",
							"Error receiving data from pipe %d: %s\n", fd, strerror(errno));
				return LE_RECV_FAIL;
			}
		}
		else {
			if ((lisobuf->buflen + ret) > lisobuf->bufsize) {
				if (extend_liso_buf(lisobuf)) 
					return LE_RECV_FAIL;
			}
			memcpy(lisobuf->liso_buf + lisobuf->buflen, buf, ret);
			lisobuf->buflen += ret;
		}
	}
	
	return LE_RECV_EOF;

}

void liso_engine_shut_down(int ret)
{
	liso_logging_log("liso_engine", "liso_engine_shut_down",
						"Liso server shut down.\n");
	liso_logging_close_log_file();
	exit(ret);
}
